package com.abpa.util.unidata.resources;

import com.abpa.util.unidata.*;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import org.springframework.beans.BeansException;
import org.springframework.context.*;

/**
 * This resource provides access to an atom feed of records in a particular file.
 * @author msowders
 */
@Path("{account}/file/{file}")
public class CollectionResource implements ApplicationContextAware {
    private static final String USER_ROLE = "UNIDATA_REST_USER";

    // The unidata session to connect to
    private Session session;
    // The name of the Session bean
    private String account;
    // The file that is being queried
    private String filename;
    // The maximum number of records to return
    private Integer maxReturn = -1;
    // A object used to build the command
    private CommandBuilder builder;
    // The response from this resource
    private String returnValue;
    // The transformer used to convert to atom
    private Transformer transformer;
    @Context
    private UriInfo context;
    private ApplicationContext appContext;
    private CacheManager cacheManager;

    /**
     * This resource provides access to an atom feed of records in a particular
     * file.
     * @param account The name of the Session bean for the session
     * @param file The file that is to be queried
     * @return a HTTP response
     */
    @GET
    @Produces("application/atom+xml")
    public Response getAtomFeed(
            @Context SecurityContext sc,
            @DefaultValue("")
            @PathParam("account") String account,
            @DefaultValue("")
            @PathParam("file") String file) {
        Logger.getLogger(CollectionResource.class.getName()).log(Level.INFO, "GET request", context.getRequestUri().toString());
        Response response;
        Cache cache = cacheManager.getCache(getClass().getName());
        if(!sc.isUserInRole(USER_ROLE)){
            throw new WebApplicationException(Response.Status.FORBIDDEN);
        }
        if (file.equals("") || account.equals("")) {
            return Response.noContent().build();
        }
        if (cache.isKeyInCache(context.getRequestUri())) {
            Element cachedResponse = cache.get(context.getRequestUri());
            if (cachedResponse != null && cachedResponse.getObjectValue() instanceof Response) {
                response = (Response) cachedResponse.getObjectValue();
            } else {
                response = Response.serverError().build();
                Logger.getLogger(CollectionResource.class.getName()).log(Level.SEVERE, "The uri:" + context.getRequestUri().toString() + " exists in cache but the value is not an instance of response or it is null.");
            }
        } else {
            setSession(account);
            setFilename(file);
            Command cmd = buildCommand();
            cmd.execute(getSession());
            try {
                response = Response.ok(buildAtomFeed(cmd)).build();
                cache.put(new Element(context.getRequestUri(), response));
            } catch (TransformerConfigurationException ex) {
                Logger.getLogger(AccountResource.class.getName()).log(Level.SEVERE, null, ex);
                // if there was an error in transformation, then the
                // document returned by the query was empty or malformed
                response = Response.noContent().build();
            } catch (TransformerException ex) {
                Logger.getLogger(AccountResource.class.getName()).log(Level.SEVERE, null, ex);
                // if there was an error in transformation, then the
                // document returned by the query was empty or malformed
                response = Response.noContent().build();
            }
        }
        return response;
    }

    private Command buildCommand() {
        MultivaluedMap<String, String> queryParameters = context.getQueryParameters(false);
        for (String field : queryParameters.keySet()) {
            if (field.equals("fields") && !queryParameters.getFirst(field).equals("")) {
                builder.requireFields(queryParameters.getFirst(field).split("\\+"));
            } else {
                // value is a '+' delimited string of the form {OPERATOR}+{VALUE1}[+{VALUE}...]
                String value = queryParameters.getFirst(field);
                String[] valueSplit = value.split("\\+");
                // the first element should be the operator
                String operator = valueSplit[0];
                // the values to be applied after the operator
                List<String> values = new ArrayList<String>();
                for (int i = 1; i < valueSplit.length; i++) {
                    values.add(valueSplit[i]);
                }
                builder.appendCondition(field, operator, values.toArray(new String[values.size()]));
            }
        }
        builder.list(getFilename()).sample(maxReturn).toXmlElements();
        return builder.build();
    }

    /**
     * Build an atom feed out of the response from unidata
     * @return an atom document
     */
    private String buildAtomFeed(Command cmd)
            throws TransformerConfigurationException,
            TransformerException {
        StringReader source = new StringReader(cmd.getResponse());
        StringWriter feed = new StringWriter();

        getTransformer().setParameter("title", filename);
        getTransformer().setParameter("resourceuri",
                getContext().getAbsolutePathBuilder().build(account, filename).toString());

        getTransformer().transform(new StreamSource(source), new StreamResult(feed));
        returnValue = feed.toString();
        return returnValue;
    }

    /**
     * @return the session
     */
    public Session getSession() {
        return session;
    }

    /**
     * @param account The name of the Session bean
     */
    public void setSession(String account) {
        session = (Session) appContext.getBean(account);
        this.account = account;
    }

    /**
     * @return the filename
     */
    public String getFilename() {
        return filename;
    }

    /**
     * @param filename the filename to set
     */
    public void setFilename(String filename) {
        this.filename = filename;
    }

    /**
     * @return the maximum number of elements to return
     */
    public Integer getMaxReturn() {
        return maxReturn;
    }

    /**
     * @param maxReturn the maximum number of elements to set
     */
    public void setMaxReturn(Integer maxReturn) {
        this.maxReturn = maxReturn;
    }

    /**
     * @return the context
     */
    public UriInfo getContext() {
        return context;
    }

    /**
     * @param context the context to set
     */
    public void setContext(UriInfo context) {
        this.context = context;
    }

    /**
     * Sets the applicationContext. This is needed to get the Session bean.
     * @param appContext
     * @throws BeansException
     */
    public void setApplicationContext(ApplicationContext appContext) throws BeansException {
        this.appContext = appContext;
    }

    /**
     * @return the transformer
     */
    public Transformer getTransformer() {
        return transformer;
    }

    /**
     * @param transformer the transformer to set
     */
    public void setTransformer(Transformer transformer) {
        this.transformer = transformer;
    }

    /**
     * @return the builder
     */
    public CommandBuilder getBuilder() {
        return builder;
    }

    /**
     * @param builder the builder to set
     */
    public void setBuilder(CommandBuilder builder) {
        this.builder = builder;
    }

    /**
     * @return the cacheManager
     */
    public CacheManager getCacheManager() {
        return cacheManager;
    }

    /**
     * @param cacheManager the cacheManager to set
     */
    public void setCacheManager(CacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }
}
